﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace Socium.Domain.Collections {
	public sealed class Cache<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> {
		#region Delegates

		public delegate void EventHandler(Cache<TKey, TValue> cache);

		public delegate void ItemEventHandler(Cache<TKey, TValue> cache, TKey key, TValue value);

		public delegate TValue ValueCreator();

		#endregion

		private Node _head;

		public int Count { get; private set; }

		public IEnumerable<TKey> Keys {
			get {
				for (var node = _head; node != null; node = node.Next)
					yield return node.Key;
			}
		}

		public IEnumerable<TValue> Values {
			get {
				for (var node = _head; node != null; node = node.Next)
					yield return node.Value;
			}
		}

		#region IEnumerable<KeyValuePair<TKey,TValue>> Members

		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
			for (var node = _head; node != null; node = node.Next)
				yield return new KeyValuePair<TKey, TValue>(node.Key, node.Value);
		}

		IEnumerator IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}

		#endregion

		public event ItemEventHandler ItemAdded;
		//public event ItemEventHandler ItemRemoved;
		public event ItemEventHandler ItemPeek;
		public event EventHandler Cleared;

		public TValue GetItem(TKey key, ValueCreator creator) {
			// ReSharper disable CompareNonConstrainedGenericWithNull
			if (key == null) {
				throw new ArgumentNullException("key");
			}
			// ReSharper restore CompareNonConstrainedGenericWithNull

			Node previous = null;
			for (var node = _head; node != null; node = node.Next) {
				if (node.Key.Equals(key)) {
					if (previous != null) {
						previous.Next = node.Next;

						node.Next = _head;
						_head = node;
					}

					if (ItemPeek != null)
						ItemPeek(this, node.Key, node.Value);

					return node.Value;
				}

				previous = node;
			}

			var value = creator();
			var new_node = new Node(key, value) { Next = _head };
			_head = new_node;

			Count++;

			if (ItemAdded != null)
				ItemAdded(this, key, value);

			return value;
		}

		public void Clear() {
			_head = null;
			Count = 0;

			if (Cleared != null)
				Cleared(this);
		}

		#region Nested type: Node

		private class Node {
			public Node(TKey key, TValue value) {
				Value = value;
				Key = key;
			}

			public TKey Key { get; private set; }
			public TValue Value { get; private set; }
			public Node Next { get; set; }

			public override string ToString() {
				return string.Format("Value: {0}, Key: {1}", Value, Key);
			}
		}

		#endregion
	}
}